// https://learn.microsoft.com/en-us/typography/opentype/spec/gsub

use super::super::ggg::*;
use super::*;

pub use kind01::*;
pub use kind02::*;
pub use kind03::*;
pub use kind04::*;
pub use kind08::*;

#[derive(Debug, Clone)]
pub struct Table<'a> {
	pub major_version: u16, // Major version of the GSUB table
	pub minor_version: u16, // Minor version of the GSUB table
	// pub script_list_offset: Option<Offset16>,        // Offset to ScriptList table, from beginning of GSUB table.
	pub script_list: Option<ScriptList<'a>>,
	// pub feature_list_offset: Option<Offset16>,       // Offset to FeatureList table, from beginning of GSUB table.
	pub feature_list: Option<FeatureList<'a>>,
	// pub lookup_list_offset: Option<Offset16>,        // Offset to LookupList table, from beginning of GSUB table.
	pub lookup_list: Option<LookupList<'a, Subtable<'a>>>,
	// Offset to FeatureVariations table, from beginning of GSUB table (may be NULL).
	pub feature_variations_offset: Option<FeatureVariations<'a>>,
}
impl<'a> Table<'a> {
	pub fn parse(data: &'a [u8]) -> Option<Self> {
		let mut s = Stream::new(data);
		let major_version = s.read()?;
		let minor_version = s.read()?;
		let this = Table {
			major_version,
			minor_version,
			script_list: s.parse_at_offset16(data),
			feature_list: s.parse_at_offset16(data),
			lookup_list: s.parse_at_offset16(data),
			feature_variations_offset: if major_version >= 1 && minor_version >= 1 {
				s.parse_at_offset32(data)
			} else {
				None
			},
		};
		Some(this)
	}
}

/// A glyph substitution
/// [lookup subtable](https://docs.microsoft.com/en-us/typography/opentype/spec/gsub#table-organization)
/// enumeration.
#[allow(missing_docs)]
#[derive(Clone, Copy, Debug)]
pub enum Subtable<'a> {
	Single(SingleSubstitution<'a>),
	Multiple(MultipleSubstitution<'a>),
	Alternate(AlternateSubstitution<'a>),
	Ligature(LigatureSubstitution<'a>),
	Context(ContextLookup<'a>),
	ChainContext(ChainedContextLookup<'a>),
	ReverseChainSingle(ReverseChainSingleSubstitution<'a>),
}
impl<'a> LookupSubtable<'a> for Subtable<'a> {
	fn parse(data: &'a [u8], kind: u16) -> Option<Self> {
		match kind {
			1 => SingleSubstitution::parse(data).map(Self::Single),
			2 => MultipleSubstitution::parse(data).map(Self::Multiple),
			3 => AlternateSubstitution::parse(data).map(Self::Alternate),
			4 => LigatureSubstitution::parse(data).map(Self::Ligature),
			5 => ContextLookup::parse(data).map(Self::Context),
			6 => ChainedContextLookup::parse(data).map(Self::ChainContext),
			7 => parse_extension_lookup(data, Self::parse),
			8 => ReverseChainSingleSubstitution::parse(data).map(Self::ReverseChainSingle),
			_ => None,
		}
	}
}
impl<'a> Subtable<'a> {
	/// Returns the subtable coverage.
	#[inline]
	pub fn coverage(&self) -> Coverage<'a> {
		match self {
			Self::Single(t) => t.coverage(),
			Self::Multiple(t) => t.coverage,
			Self::Alternate(t) => t.coverage,
			Self::Ligature(t) => t.coverage,
			Self::Context(t) => t.coverage(),
			Self::ChainContext(t) => t.coverage(),
			Self::ReverseChainSingle(t) => t.coverage,
		}
	}
	/// Checks that the current subtable is *Reverse Chaining Contextual Single*.
	#[inline]
	pub fn is_reverse(&self) -> bool {
		matches!(self, Self::ReverseChainSingle(_))
	}
}
mod kind01 {
	use super::*;

	/// A [Single Substitution Subtable](https://docs.microsoft.com/en-us/typography/opentype/spec/gsub#SS).
	#[allow(missing_docs)]
	#[derive(Clone, Copy, Debug)]
	pub enum SingleSubstitution<'a> {
		Format1 {
			coverage: Coverage<'a>,
			delta: i16,
		},
		Format2 {
			coverage: Coverage<'a>,
			substitutes: LazyArray16<'a, GlyphId>,
		},
	}
	impl<'a> SingleSubstitution<'a> {
		pub fn parse(data: &'a [u8]) -> Option<Self> {
			let mut s = Stream::new(data);
			match s.read::<u16>()? {
				1 => Some(Self::Format1 {
					coverage: s.parse_at_offset16(data)?,
					delta: s.read()?,
				}),
				2 => Some(Self::Format2 {
					coverage: s.parse_at_offset16(data)?,
					substitutes: s.parse_array16()?,
				}),
				_ => None,
			}
		}
		/// Returns the subtable coverage.
		#[inline]
		pub fn coverage(&self) -> Coverage<'a> {
			match self {
				Self::Format1 { coverage, .. } => *coverage,
				Self::Format2 { coverage, .. } => *coverage,
			}
		}
	}
}
mod kind02 {
	use super::*;

	/// A [Multiple Substitution Subtable](https://docs.microsoft.com/en-us/typography/opentype/spec/gsub#MS).
	#[allow(missing_docs)]
	#[derive(Clone, Copy, Debug)]
	pub struct MultipleSubstitution<'a> {
		pub coverage: Coverage<'a>,
		pub sequences: SequenceList<'a>,
	}

	impl<'a> MultipleSubstitution<'a> {
		pub fn parse(data: &'a [u8]) -> Option<Self> {
			let mut s = Stream::new(data);
			match s.read::<u16>()? {
				1 => Some(Self {
					coverage: s.parse_at_offset16(data)?,
					sequences: SequenceList::new(data, s.parse_array16()?),
				}),
				_ => None,
			}
		}
	}

	/// A sequence of glyphs for
	/// [Multiple Substitution Subtable](https://docs.microsoft.com/en-us/typography/opentype/spec/gsub#MS).
	pub type Sequence<'a> = LazyArray16<'a, GlyphId>;

	/// A list of [`Sequence`] tables.
	pub type SequenceList<'a> = LazyOffsetArray16<'a, Sequence<'a>>;
}
mod kind03 {
	use super::*;

	/// A [Alternate Substitution Subtable](https://docs.microsoft.com/en-us/typography/opentype/spec/gsub#AS).
	#[allow(missing_docs)]
	#[derive(Clone, Copy, Debug)]
	pub struct AlternateSubstitution<'a> {
		pub coverage: Coverage<'a>,
		pub alternate_sets: AlternateSets<'a>,
	}

	impl<'a> AlternateSubstitution<'a> {
		pub fn parse(data: &'a [u8]) -> Option<Self> {
			let mut s = Stream::new(data);
			match s.read::<u16>()? {
				1 => Some(Self {
					coverage: s.parse_at_offset16(data)?,
					alternate_sets: AlternateSets::new(data, s.parse_array16()?),
				}),
				_ => None,
			}
		}
	}

	/// A list of glyphs for
	/// [Alternate Substitution Subtable](https://docs.microsoft.com/en-us/typography/opentype/spec/gsub#AS).
	pub type AlternateSet<'a> = LazyArray16<'a, GlyphId>;

	/// A set of [`AlternateSet`].
	pub type AlternateSets<'a> = LazyOffsetArray16<'a, AlternateSet<'a>>;
}
mod kind04 {
	use super::*;

	/// A [Ligature Substitution Subtable](https://docs.microsoft.com/en-us/typography/opentype/spec/gsub#LS).
	#[allow(missing_docs)]
	#[derive(Clone, Copy, Debug)]
	pub struct LigatureSubstitution<'a> {
		pub coverage: Coverage<'a>,
		pub ligature_sets: LigatureSets<'a>,
	}

	impl<'a> LigatureSubstitution<'a> {
		pub fn parse(data: &'a [u8]) -> Option<Self> {
			let mut s = Stream::new(data);
			match s.read::<u16>()? {
				1 => Some(Self {
					coverage: s.parse_at_offset16(data)?,
					ligature_sets: LigatureSets::new(data, s.parse_array16()?),
				}),
				_ => None,
			}
		}
	}

	/// Glyph components for one ligature.
	#[derive(Clone, Copy, Debug)]
	pub struct Ligature<'a> {
		/// Ligature to substitute.
		pub glyph: GlyphId,
		/// Glyph components for one ligature.
		pub components: LazyArray16<'a, GlyphId>,
	}

	impl<'a> FromSlice<'a> for Ligature<'a> {
		fn parse(data: &'a [u8]) -> Option<Self> {
			let mut s = Stream::new(data);
			let glyph = s.read::<GlyphId>()?;
			let count = s.read::<u16>()?;
			let components = s.read_array16(count.checked_sub(1)?)?;
			Some(Self { glyph, components })
		}
	}

	/// A [`Ligature`] set.
	pub type LigatureSet<'a> = LazyOffsetArray16<'a, Ligature<'a>>;

	/// A list of [`Ligature`] sets.
	pub type LigatureSets<'a> = LazyOffsetArray16<'a, LigatureSet<'a>>;
}
mod kind08 {
	use super::*;

	/// A [Reverse Chaining Contextual Single Substitution Subtable](
	/// https://docs.microsoft.com/en-us/typography/opentype/spec/gsub#RCCS).
	#[allow(missing_docs)]
	#[derive(Clone, Copy, Debug)]
	pub struct ReverseChainSingleSubstitution<'a> {
		pub coverage: Coverage<'a>,
		pub backtrack_coverages: LazyOffsetArray16<'a, Coverage<'a>>,
		pub lookahead_coverages: LazyOffsetArray16<'a, Coverage<'a>>,
		pub substitutes: LazyArray16<'a, GlyphId>,
	}

	impl<'a> ReverseChainSingleSubstitution<'a> {
		pub fn parse(data: &'a [u8]) -> Option<Self> {
			let mut s = Stream::new(data);
			match s.read::<u16>()? {
				1 => Some(Self {
					coverage: s.parse_at_offset16(data)?,
					backtrack_coverages: LazyOffsetArray16::new(data, s.parse_array16()?),
					lookahead_coverages: LazyOffsetArray16::new(data, s.parse_array16()?),
					substitutes: s.parse_array16()?,
				}),
				_ => None,
			}
		}
	}
}
